/*
 *	MemMgr.cpp is part of Flight_Chess_Portable
 *	Copyright (C) 2008  Poison
 *
 *	This program is free software: you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation, either version 3 of the License, or
 *	(at your option) any later version.
 *
 *	This program is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *	GNU General Public License for more details.
 *
 *	You should have received a copy of the GNU General Public License
 *	along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
 *	Class:			MemMgr
 *	Description:	
 *	Author:			Poison <hbpoison@gmail.com>
 *	Date Created:	2008-02-20
 */

#include "MemMgr.h"

#include <pspsysmem.h>
#include <pspsysmem_kernel.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>

MemMgr::~MemMgr()
{
	for ( int i = count - 1; i >= 0; i -- )
	{
		free( records[i].addr );
		delRecord( i );
	}
}

void * MemMgr::alloc( unsigned int size )
{
	void * addr = malloc( size );
	if ( addr )
	{
		addRecord( addr, size, -1 );
	}
	return addr;
}

int MemMgr::free( void * ptr )
{
	int index = indexOf( ptr );
	if ( index >= 0 )
	{
		delRecord( index );
		free( ptr );
		return 0;
	}
	return -1;
}

int MemMgr::indexOf( void * ptr )
{
	for ( int i = count - 1; i >= 0; i -- )
	{
		if ( records[i].addr == ptr )
			return i;
	}
	return -1;
}

void MemMgr::addRecord( void * addr, unsigned int size, int id )
{
	AllocRecord record;
	record.addr = addr;
	record.size = size;
	record.id = id;
	allocated_size += size;
	count ++;
	records.push_back( record );
}

void MemMgr::delRecord( int index )
{
	count --;
	allocated_size -= records[index].size;
	records.erase( records.begin() + index );
}

UMemMgr::UMemMgr()
{
	partition_id = 2;
}

UMemMgr::~UMemMgr()
{
	for ( int i = count - 1; i >= 0; i -- )
	{
		sceKernelFreePartitionMemory( records[i].id );
		delRecord( i );
	}
}

void * UMemMgr::alloc( unsigned int size )
{
	char block_name[16];
	sprintf( block_name, "memmgr_alloc_%04d_%d", rand() % 10000, count );
	int id = sceKernelAllocPartitionMemory( partition_id, block_name, PSP_SMEM_Low, size, NULL );
	if ( id >= 0 )
	{
		void * addr = sceKernelGetBlockHeadAddr( id );
		addRecord( addr, size, id );
		return addr;
	}
	return NULL;
}

int UMemMgr::free( void * ptr )
{
	int index = indexOf( ptr );
	if ( index >= 0 )
	{
		int ret = sceKernelFreePartitionMemory( records[index].id );
		if ( ret >= 0 )
		{
			delRecord( index );
			return 0;
		}
	}
	return -1;
}

#ifdef _KERNEL_MODE_
//KMemMgr can only be used in kernel module

KMemMgr::KMemMgr()
{
	partition_id = 1;
	heap_id = -1;
	total_size = 0;
}

KMemMgr::KMemMgr( int pid)
{
	KMemMgr::KMemMgr();
	unlockPartition( pid );
	partition_id = pid;
}

KMemMgr::KMemMgr( unsigned int size, int pid, const char * heap_name )
{
	KMemMgr::KMemMgr();
	unlockPartition( pid );
	partition_id = pid;
	heap_id = sceKernelCreateHeap( partition_id, size, 1, heap_name );
	if ( heap_id >= 0 )
		total_size = size;
}

KMemMgr::~KMemMgr()
{
	if ( heap_id >= 0 )
	{
		for ( int i = count - 1; i >= 0; i -- )
		{
			sceKernelFreeHeapMemory( heap_id, records[i].addr );
			delRecord( i );
		}
		total_size = 0;
	}
}

void * KMemMgr::alloc( unsigned int size )
{
	if ( heap_id >= 0 )
	{
		void * addr = sceKernelAllocHeapMemory( heap_id, size );
		if ( addr )
		{
			addRecord( addr, size, -1 );
			return addr;
		}
	}
	else
		UMemMgr::alloc( size );
	return NULL;
}

int KMemMgr::free( void * ptr )
{
	if ( heap_id >= 0 )
	{
		int index = indexOf( ptr );
		if ( index )
		{
			int ret = sceKernelFreeHeapMemory( heap_id, ptr );
			if ( ret >= 0 )
			{
				delRecord( index );
				return 0;
			}
		}
	}
	else
		UMemMgr::free( ptr );
	return -1;
}

void KMemMgr::unlockPartition( int pid )
{
	PspSysmemPartitionInfo p_info;
	memset( &p_info, 0, sizeof( PspSysmemPartitionInfo ) );
	p_info.size = sizeof( p_info );
	sceKernelQueryMemoryPartitionInfo( pid, &p_info );
	sceKernelSetDdrMemoryProtection( ( void * )p_info.startaddr, p_info.memsize, 0xF );
}

#endif //_KERNEL_MODE_
